﻿using System;
using IQIGame.Onigao.Framework;
using IQIGame.Onigao.GamePlay;
using LFloatMath.Math;


namespace IQIGame.Onigao.Logic
{
    public static class DamageController
    {
        public static DamageResponse TakeDamageBySkill(Unit attacker, Unit defender, Skill skill, SkillCorrect correct, int hitFormula, int critFormula, int damageFormula, bool checkDead)
        {
            var rAtkType = (EDamageFrom)(1 << skill.config.SkillType);
            var rDamageType = (EDamageType)(1 << skill.config.DamageType);
            var rResp = TakeDamage(attacker, defender, rAtkType, hitFormula, critFormula, damageFormula, rDamageType, skill, null, checkDead, correct.tempDamageModify, correct.tempHurtModify);
            return rResp;
        }

        public static DamageResponse TakeDamage(Unit attacker, Unit defender, EDamageFrom from, int hitFormula = 0, int critFormula = 0, int damageFormula = 0, EDamageType damageType = EDamageType.Default, Skill skill = null, BuffHandler buff = null, bool checkDead = true, int param1 = 0, int param2 = 0)
        {
            bool bHit = true;
            if (hitFormula > 0)
            {
                bHit = Formula.Calculate<bool, int, int>(hitFormula, attacker, defender, skill, param1, param2);
            }
            bool bCrit = false;
            if (critFormula > 0)
            {
                bCrit = Formula.Calculate<bool, int, int>(critFormula, attacker, defender, skill, param1, param2);
            }
            var rBackstab = EBackstabType.Front;
            if (skill != null)
            {
                rBackstab = defender.CheckBackstab(attacker.pos - defender.pos);
            }
            var nDamage = Formula.Calculate<LFloat, int, int>(damageFormula, attacker, defender, skill, param1, param2);

            var nConfigID = 0;
            if (from == EDamageFrom.Buff)
            {
                nConfigID = buff?.buffID ?? 0;
            }
            else
            {
                nConfigID = skill?.skillID ?? 0;
            }
            var rResp = TakeDamageWithoutFormula(attacker, defender, from, bHit, bCrit, nDamage, damageType, nConfigID, checkDead, rBackstab);
            return rResp;
        }

        public static DamageResponse TakeDamageWithoutFormula(Unit attacker, Unit defender, EDamageFrom damageFrom, bool hit, bool crit, LFloat damage,
            EDamageType damageType = EDamageType.Default, int configID = 0, bool checkDead = true, EBackstabType backstab = EBackstabType.Front)
        {
            var rResp = ClassPool.Get<DamageResponse>();
            rResp.attacker = attacker.GUID;
            rResp.defender = defender.GUID;
            rResp.configID = configID;
            rResp.damageFrom = damageFrom;
            rResp.damageType = damageType;
            rResp.dodge = !hit;
            rResp.backstab = backstab;
            if (hit)
            {
                rResp.crit = crit;
                rResp.baseDamage = damage;
                var fCritDamage = LFloat.one;
                if (rResp.crit)
                {
                    fCritDamage += attacker.GetAttr().Get(EAttributeType.CritDamage) / LFloat.tenThousand;
                }
                rResp.finalDamage = (rResp.baseDamage * fCritDamage * (defender.InWeaknessBreak() ? BattleConst.WeaknessDamageRate : LFloat.one)).ToInt();
                Damage(attacker, defender, rResp, checkDead);
            }
#if UNITY_EDITOR
            EditorLog.AppendDamageLog(rResp);
#endif
            return rResp;
        }

        public static void RemoveHpWithoutDamage(Unit rUnit, int nValue)
        {
            var rAttr = rUnit.GetComponent<AttributeComponent>(ETComponentType.AttributeComponent);
            rAttr.Add(EAttributeType.Hp, -nValue);
        }

        public static DamageResponse TakeDamageWithoutShield(Unit attacker, Unit defender, EDamageFrom damageFrom, EDamageType damageType, int value, Skill skill, bool checkDead)
        {
            var rResp = ClassPool.Get<DamageResponse>();
            rResp.attacker = attacker.GUID;
            rResp.defender = defender.GUID;
            rResp.damageFrom = damageFrom;
            rResp.damageType = damageType;
            rResp.configID = skill?.skillID ?? 0;
            rResp.baseDamage = value.ToLFloat();
            rResp.finalDamage = rResp.baseDamage.ToInt();
            DamageWithoutShield(attacker, defender, rResp, checkDead);
#if UNITY_EDITOR
            EditorLog.AppendDamageLog(rResp);
#endif
            return rResp;
        }


        public static CureResponse TakeCure(Unit rAttacker, Unit rDefender, int nCureFormula, int param)
        {
            return TakeCureWithoutFormula(rAttacker, rDefender, Formula.Calculate<LFloat, int>(nCureFormula, rAttacker, rDefender, param));
        }

        private static CureResponse TakeCureWithoutFormula(Unit attacker, Unit defender, LFloat baseCure)
        {
            var rResp = ClassPool.Get<CureResponse>();
            rResp.attacker = attacker.GUID;
            rResp.defender = defender.GUID;

            rResp.baseCure = baseCure;
            rResp.finalCure = rResp.baseCure.ToInt();

            var rDefAttr = defender.GetComponent<AttributeComponent>(ETComponentType.AttributeComponent);
            var nMax = rDefAttr.Get(EAttributeType.MaxHp);
            var nCurHp = rDefAttr.Get(EAttributeType.Hp);
            var nCure = Math.Clamp(rResp.finalCure, 0, (nMax - nCurHp).ToInt());
            rResp.realCure = nCure;
            attacker.AddStatCure(nCure);
            rDefAttr.Add(EAttributeType.Hp, nCure);
#if UNITY_EDITOR
            EditorLog.AppendDamageLog(rResp);
#endif
            return rResp;
        }

        public static void TakeWeaknessHit(Unit attacker, Unit defender, EDamageType damageType, int value)
        {
            if (!defender.InWeaknessBreak() && (defender.weaknessType & damageType) != 0)
            {
                defender.weaknessPoint -= value;
                if (defender.weaknessPoint <= 0)
                {
                    defender.WeaknessBreak(attacker, damageType);
                }
            }
        }

        private static void Damage(Unit attacker, Unit defender, DamageResponse damageResp, bool checkDead)
        {
            var nDamage = Math.Max(1, damageResp.finalDamage);
            var rBuffComp = defender.GetComponent<BuffComponent>(ETComponentType.BuffComponent);
            if (rBuffComp != null)
            {
                nDamage = rBuffComp.shield.Damage(nDamage);
            }
            var rDefAttr = defender.GetComponent<AttributeComponent>(ETComponentType.AttributeComponent);
            var nCurHp = rDefAttr.Get(EAttributeType.Hp).ToInt();
            nDamage = Math.Min(nCurHp, nDamage);
            rDefAttr.Add(EAttributeType.Hp, -nDamage);
            damageResp.realDamage = nDamage;
            attacker.AddStatDamage(nDamage);
            defender.AddStatHurt(nDamage);
            CheckDead(attacker, defender, checkDead);
        }

        private static void DamageWithoutShield(Unit attacker, Unit defender, DamageResponse damageResp, bool checkDead)
        {
            var nDamage = Math.Max(1, damageResp.finalDamage);
            var rDefAttr = defender.GetComponent<AttributeComponent>(ETComponentType.AttributeComponent);
            var nCurHp = rDefAttr.Get(EAttributeType.Hp).ToInt();
            nDamage = Math.Min(nCurHp, nDamage);
            rDefAttr.Add(EAttributeType.Hp, -nDamage);
            damageResp.realDamage = nDamage;
            attacker.AddStatDamage(nDamage);
            defender.AddStatHurt(nDamage);
            CheckDead(attacker, defender, checkDead);
        }

        public static bool CheckDead(Unit attacker, Unit defender, bool deadNow)
        {
            if (defender.CheckDead())
            {
                var rDead = defender.AddComponent<DeadComponent>(ETComponentType.DeadComponent);
                rDead.Initialize(attacker, deadNow);
                return true;
            }
            return false;
        }
    }
}